跳到主要内容

SpringSecurity 配置项

总的类图

把 Spring Security 的核心领域模型设计整理如下:

核心架构

权限系统一般包含两大核心模块:认证(Authentication)和鉴权(Authorization)。

  • 认证:认证模块负责验证用户身份的合法性,生成认证令牌,并保存到服务端会话中(如TLS)。
  • 鉴权:鉴权模块负责从服务端会话内获取用户身份信息,与访问的资源进行权限比对。

官方给出的 Spring Security 的核心架构图如下:

核心架构解读:

  • AuthenticationManager:负责认证管理,解析用户登录信息(封装在 Authentication),读取用户、角色、权限信息进行认证,认证结果被回填到 Authentication,保存在 SecurityContext。
  • AccessDecisionManager:负责鉴权投票表决,汇总投票器的结果,实现一票通过(默认)、多票通过、一票否决策略。
  • SecurityInterceptor:负责权限拦截,包括 Web URL 拦截和方法调用拦截。通过 ConfigAttributes 获取资源的描述信息,借助于 AccessDecisionManager 进行鉴权拦截。(就是将请求委托给各个具体的鉴权的 AccessDecisionManager)
  • SecurityContext:安全上下文,保存认证结果。提供了全局上下文、线程继承上下文、线程独立上下文(默认)三种策略。
  • Authentication:认证信息,保存用户的身份标示、权限列表、证书、认证通过标记等信息。
  • SecuredResource:被安全管控的资源,如 Web URL、用户、角色、自定义领域对象等。
  • ConfigAttributes:资源属性配置,描述安全管控资源的信息,为 SecurityInterceptor 提供拦截逻辑的输入。

委托过滤器 SecurityInterceptor

经常能看到下面这张图

实际上通过 Spring Security 内置的一个 Security Interceptor 委托给各个具体的 AccessDecisionManager

配置 AuthenticationConfiguration

AuthenticationConfiguration 负责认证系统的全局配置,GlobalMethodSecurityConfiguration 负责方法调用拦截的全局配置。

构建 AuthenticationManagerBuilder

AuthenticationConfiguration 通过 AuthenticationManagerBuilder 构建认证管理器 AuthenticationManager,GlobalMethodSecurityConfiguration 会自动初始化 AbstractSecurityInterceptor 进行方法调用拦截。

Web 拦截 HttpSecurity

HttpSecurity配置列表:

方法拦截

Spring 通过 AOP技术(cglib/aspectj)对标记为 @PreAuthorize@PreFilter@PostAuthorize@PostFilter 等注解的方法进行拦截,通过 AbstractSecurityInterceptor 调用 AuthenticationManager 进行身份认证(如果必要的话)。

认证 AuthenticationManager

认证管理器 AuthenticationManager 内置了多种认证器 AuthenticationProvider,只要其中一个认证通过,认证便成功。不同的 AuthenticationProvider 获取各自需要的信息(HTTP请求、数据库查询、远程服务等)进行认证,认证结果全部封装在 Authentication。

需要加载用户、角色、权限信息的认证器(如密码认证、预认证等)需要对接 UserDetailsManager 接口实现用户 CRUD 功能。

鉴权 AbstractSecurityInterceptor

权限拦截器 AbstractSecurityInterceptor 通过读取不同的 SecurityMetadataSource 加载需要被鉴权资源的描述信息 ConfigAttribute,然后把认证信息 Authentication、资源描述 ConfigAttribute、资源对象本身传递给 AccessDecisionManager 进行表决。

AccessDecisionManager 内置了多个投票器 AccessDecisionVoter,投票器会将鉴权信息中的 ConfigAttribute 转换为 SpringEL 的格式,通过表达式处理器 SecurityExpressionHandler 执行基于表达式的鉴权逻辑,鉴权逻辑会通过反射的方式转发到 SecurityExpressionRoot 的各个操作上去。

定制点 WebSecurityConfigureAdapter ⭐

通过 WebSecurityConfigureAdapter 可以定制 HTTP 安全配置 HttpSecurity 和认证管理器生成器 AuthenticationManagerBuilder;

通过 AbstractPreAuthenticatedProcessingFilter 可以定制预认证过滤器;

通过 UserDetailsManager 和 UserDetails 接口可以对接自定义数据源;

通过 GrantedAuthority 定制权限信息;

通过 PermissionEvaluator 可以定制自定义领域模型的访问控制逻辑。

WebSecurityConfigurer 是什么?

这个是一个 WebSecurityConfigurer 接口

public interface WebSecurityConfigurer<T extends SecurityBuilder<Filter>> extends
SecurityConfigurer<Filter, T> {
}

官方描述:允许自定义到网络安全。在大多数情况下,用户将使用 EnableWebSecurity 并创建一个扩展 WebSecurityConfigurerAdapter 的配置,该配置将通过 @EnableWebSecurity 注释自动应用到 WebSecurity。

说白了它就是一个对 WebSecurity 进行配置的默认接口(设计模式...),一般无需手动去实现这个接口,官方提供了一个 WebSecurityConfigurerAdapter 适配器帮你默认实现了这个接口

WebSecurityConfigurerAdapter 是什么?

参考资料 Spring Security Config : WebSecurityConfigurerAdapter

可以发现常用的 WebSecurityConfigurerAdapter 实际上就是一个 WebSecurityConfigurer 的适配器,它内部提供了一堆默认配置,如果要自定义某块地方直接重现它的方法就行了,说白了就是官方给你做的一个默认配置,不然手动实现 WebSecurityConfigurer 里面的配置项就很麻烦了

例如点进这个 configure 进去,可以发现它默认就使用了 formLogin 和 httpBasic 这两个过滤器

protected void configure(HttpSecurity http) throws Exception {
this.logger.debug("Using default configure(HttpSecurity). "
+ "If subclassed this will potentially override subclass configure(HttpSecurity).");
http.authorizeRequests((requests) -> requests.anyRequest().authenticated());
http.formLogin();
http.httpBasic();
}

然后用户可以重写上面的这个默认配置

@Configuration
@EnableWebSecurity
public class WebSecurityConfigextends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/resources/**", "/signup", "/about").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
.anyRequest().authenticated()
.and()
.formLogin()
.usernameParameter("username")
.passwordParameter("password")
.failureForwardUrl("/login?error")
.loginPage("/login")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/index")
.permitAll()
.and()
.httpBasic()
.disable();
}
}

这段配置,就是配置 Security 的认证策略,每个模块配置使用 and 结尾。

  • authorizeRequests() 配置路径拦截,表明路径访问所对应的权限、角色、认证信息。
  • formLogin() 对应表单认证相关的配置
  • logout() 对应了注销相关的配置
  • httpBasic() 可以配置 basic 登录

配置 AuthenticationManager 构建器

在认证那一节学过 AuthenticationManager (接口)是认证相关的核心接口,它的作用是定义了一个认证方法,它将一个未认证的 Authentication 传入,返回一个已认证的 Authentication。

然后手动去实现这个接口还是有点麻烦(其实也不麻烦,但是手动配置的有点简陋),所以 SpringSecurity 默认提供了一个 AuthenticationManagerBuilder 建造者去构建出一个 AuthenticationManager,了解过设计模式的都知道,每个建造者实现类都是实现了 “蓝图”(Builder 抽象类)的具体 “施工方案”,所以它和上面的 WebSecurityConfigurerAdapter 有点像,都是默认给你配置好了

虽然这个建造者没有上面适配器那样简单粗暴直接允许用户重写某个方法,但是这个它给用户提供了一些方法用来替换 “蓝图” 里的零部件,如下调用的这个 inMemoryAuthentication 方法,它可以让这个 Builder 在构建 AuthenticationManager 时选择使用 MemoryAuthentication 这个实现类

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("admin").password("admin").roles("USER");
}
}

点进这个 inMemoryAuthentication 方法可以发现它调用了内部的 apply 方法替换了建造时的 “原材料”

public InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> inMemoryAuthentication()
throws Exception {
return apply(new InMemoryUserDetailsManagerConfigurer<>());
}

值得一提的是它默认使用的 AuthenticationManager 实际是 ProviderManager,可以看它默认提供的 userDetailsService 方法返回值是 DaoAuthenticationConfigurer(它们的关系看认证那一篇笔记),而上面调用了 inMemoryAuthentication 方法后返回值实际上是 InMemoryUserDetailsManagerConfigurer

public <T extends UserDetailsService> DaoAuthenticationConfigurer<AuthenticationManagerBuilder, T> userDetailsService(
T userDetailsService) throws Exception {
this.defaultUserDetailsService = userDetailsService;
return apply(new DaoAuthenticationConfigurer<>(userDetailsService));
}

所以在 WebSecurityConfigurerAdapter 里面像下面这样写(挺常用的)实际就是默认选择了 ProviderManager

/*  认证那篇笔记最后面的代码 */
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService()).passwordEncoder(passwordEncoder());
}
/* ...*/

ResourceServerConfigurerAdapter

这个是资源服务器的配置端点